In [71]:
""" Boiler plate
- import all packages
- emulate snakemake so can load config as usual
- load the network you want
TODO: make it easier to restore old runs
TODO: hide all the boiler pates in a file
"""
%load_ext autoreload
%autoreload 2
%precision %e
import logging
import pypsa
import os.path

import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import pandas as pd
# running the jupyter notebook on the compute nodes doesnt build the path as expected, you have to manually do this
import IPython

working_directory = os.path.dirname(IPython.extract_module_locals()[1]['__vsc_ipynb_file__'])
workflow_dir = os.path.dirname(working_directory)
scripts_dir = os.path.join(workflow_dir, "scripts")
root_dir = os.path.dirname(workflow_dir)

os.chdir(scripts_dir)


logging.basicConfig(level=logging.CRITICAL)
logger = logging.getLogger(__name__)


from _helpers import configure_logging, mock_snakemake
# from make_summary import assign_carriers
import _plot_utilities
import plot_network 
logging.getLogger('plot_network').setLevel(logging.CRITICAL)
logging.getLogger('_plot_utilities').setLevel(logging.CRITICAL)

from constants import PLOT_COST_UNITS, PLOT_CAP_UNITS,PLOT_SUPPLY_UNITS
from _plot_utilities import fix_network_names_colors, determine_plottable
from plot_network import plot_cost_map, plot_map
The autoreload extension is already loaded. To reload it, use:
  %reload_ext autoreload
In [72]:
REMIND_COUPLED = True
PLANNING_YEAR = 2035
config = None if not REMIND_COUPLED else "resources/tmp/remind_coupled.yaml"
In [73]:
snakemake = mock_snakemake(
    "plot_network",
    snakefile_path=workflow_dir,
    topology="current+FCG",
    # co2_pathway="exp175default",
    co2_pathway="SSP2-PkBudg1000-PyPS",
    planning_horizons=PLANNING_YEAR,
    configfiles = config,
    heating_demand="positive",
)

configure_logging(snakemake, logger=logger)
config = snakemake.config
tech_colors = config["plotting"]["tech_colors"]


ntw_path = snakemake.input.network
# ntw_path = f"/home/ivanra/downloads/PaperResultsXiaowei_networks/postnetwork-ll-current+Neighbor-exponential175-{PLANNING_YEAR}.nc"
# ntw_path = "/p/tmp/ivanra/xiaowei-pypsa/PyPSA-China/results/version-0325.175.1H/postnetworks/positive/postnetwork-ll-current+Neighbor-exponential175-2060.nc"
n = pypsa.Network(ntw_path)
results_dir = os.path.dirname(os.path.dirname(ntw_path))
 
2025-06-06 08:55:47,990 - _helpers.py - INFO - =========== NEW RUN ===========
2025-06-06 08:55:47,990 - _helpers.py - INFO - =========== NEW RUN ===========
INFO:__main__:=========== NEW RUN ===========

Fix network for plotting // add missing info¶

In [74]:
fix_network_names_colors(n, config)
determine_plottable(n)
/p/tmp/ivanra/PyPSA-China-PIK/workflow/scripts/_plot_utilities.py:232: FutureWarning: A value is trying to be set on a copy of a DataFrame or Series through chained assignment using an inplace method.
The behavior will change in pandas 3.0. This inplace method will never work because the intermediate object on which we are setting values always behaves as a copy.

For example, when doing 'df[col].method(value, inplace=True)', try using 'df.method({col: value}, inplace=True)' or df[col] = df[col].method(value) instead, to perform the operation inplace on the original object.


  n.carriers.color.fillna(NAN_COLOR, inplace=True)

Plot maps¶

plot capex¶

In [75]:
ax = plot_cost_map(n, config["plotting"], cost_pannel=True, capex_only=True)
/home/ivanra/.conda/envs/pypsa-china/lib/python3.12/site-packages/cartopy/mpl/feature_artist.py:144: UserWarning: facecolor will have no effect as it has been defined as "never".
  warnings.warn('facecolor will have no effect as it has been '
/home/ivanra/.conda/envs/pypsa-china/lib/python3.12/site-packages/cartopy/mpl/feature_artist.py:144: UserWarning: facecolor will have no effect as it has been defined as "never".
  warnings.warn('facecolor will have no effect as it has been '
/p/tmp/ivanra/PyPSA-China-PIK/workflow/scripts/plot_network.py:190: UserWarning: This figure includes Axes that are not compatible with tight_layout, so results might be incorrect.
  fig.tight_layout()
/p/tmp/ivanra/PyPSA-China-PIK/workflow/scripts/plot_network.py:349: UserWarning: This figure includes Axes that are not compatible with tight_layout, so results might be incorrect.
  fig.tight_layout()
No description has been provided for this image
In [76]:
# TODO fix layout
ax = plot_cost_map(n, config["plotting"], cost_pannel=True, plot_additions=False, capex_only=False)
/home/ivanra/.conda/envs/pypsa-china/lib/python3.12/site-packages/cartopy/mpl/feature_artist.py:144: UserWarning: facecolor will have no effect as it has been defined as "never".
  warnings.warn('facecolor will have no effect as it has been '
/p/tmp/ivanra/PyPSA-China-PIK/workflow/scripts/plot_network.py:190: UserWarning: This figure includes Axes that are not compatible with tight_layout, so results might be incorrect.
  fig.tight_layout()
/p/tmp/ivanra/PyPSA-China-PIK/workflow/scripts/plot_network.py:349: UserWarning: This figure includes Axes that are not compatible with tight_layout, so results might be incorrect.
  fig.tight_layout()
No description has been provided for this image

plot electricy generation map¶

In [77]:
from plot_network import plot_energy_map
from _plot_utilities import set_plot_style
set_plot_style(
    style_config_file= os.path.abspath("../../config/plotting_styles/network_map.mplstyle"),
    base_styles=["classic", "seaborn-v0_8-white"],
)
plot_energy_map(n,config["plotting"], carrier="AC", components = ["Generator", "Link"])
/home/ivanra/.conda/envs/pypsa-china/lib/python3.12/site-packages/cartopy/mpl/feature_artist.py:144: UserWarning: facecolor will have no effect as it has been defined as "never".
  warnings.warn('facecolor will have no effect as it has been '
/p/tmp/ivanra/PyPSA-China-PIK/workflow/scripts/plot_network.py:504: UserWarning: This figure includes Axes that are not compatible with tight_layout, so results might be incorrect.
  fig.tight_layout()
/p/tmp/ivanra/PyPSA-China-PIK/workflow/scripts/plot_network.py:465: UserWarning: This figure includes Axes that are not compatible with tight_layout, so results might be incorrect.
  fig.tight_layout()
No description has been provided for this image
In [78]:
from plot_network import plot_energy_map
from _plot_utilities import set_plot_style

if snakemake.config["heat_coupling"]:
    set_plot_style(
        style_config_file= os.path.abspath("../../config/plotting_styles/network_map.mplstyle"),
        #snakemake.config["plotting"]["network_style_config_file"],
        base_styles=["classic", "seaborn-v0_8-white"],
    )
    plot_energy_map(n,config["plotting"], carrier="heat", components = ["Generator", "Link"])

Nodal prices¶

In [79]:
from plot_network import plot_nodal_prices
plot_nodal_prices(n, config["plotting"], "AC")
/home/ivanra/.conda/envs/pypsa-china/lib/python3.12/site-packages/cartopy/mpl/feature_artist.py:144: UserWarning: facecolor will have no effect as it has been defined as "never".
  warnings.warn('facecolor will have no effect as it has been '
No description has been provided for this image

Interactive plot map¶

In [80]:
import numpy as np
# make names and link sizes, make sure we only plot AC, DC & statiosn
ac_links = n.links[n.links.carrier == "AC"]
colors = n.links.index.to_series().apply(lambda x: 'black' if 'ext' in x else 'pink')
widths = np.log(n.links.p_nom_opt + 3) / 2
widths[~widths.index.isin(ac_links.index)] = 0
widths[widths.index.str.contains('reversed')] = 0
names = n.links.copy()
names["name"] = names.index.values
names.loc[~names.index.isin(ac_links.index), "p_nom_opt"] = ""
names.loc[~names.index.isin(ac_links.index), "name"] = ""
buses = n.buses.copy()
buses["name"] = buses.apply(lambda x: f"{x.name}" if x.carrier == "AC" or x.carrier=="stations" else "", axis=1)
buses["sizes"] = buses.apply(lambda x:10 if x.carrier == "AC" or x.carrier=="stations" else 0, axis=1)
buses["colors"] = buses.apply(lambda x: "black" if x.carrier == "AC" else "red", axis=1)
buses.carrier.unique()
ax = n.iplot(link_colors=colors, link_widths=widths, link_text = names.p_nom_opt.astype(str) + names.name, bus_text=buses.name, bus_sizes=buses.sizes, bus_colors=buses.colors)
/p/tmp/ivanra/anaconda/ipykernel_2927777/316105652.py:10: FutureWarning: Setting an item of incompatible dtype is deprecated and will raise an error in a future version of pandas. Value '' has dtype incompatible with float64, please explicitly cast to a compatible dtype first.
  names.loc[~names.index.isin(ac_links.index), "p_nom_opt"] = ""

extendable lines¶

In [81]:
fig, ax = plt.subplots(1, 1, figsize=(10, 10),subplot_kw={"projection":ccrs.PlateCarree()})
def width(x):
    return np.log10(x/1e6).where(x>1,0)
n.plot(ax = ax,link_colors=n.links.p_nom_extendable.apply(lambda x: "red" if not x else "black"),line_colors=n.lines.s_nom_extendable.apply(lambda x: "red" if not x else "black"), link_widths= width(n.links.p_nom_opt) , line_widths=width(n.lines.s_nom_opt), link_alpha=0.5)
# Create custom legend
import matplotlib.patches as mpatches
extendable_patch = mpatches.Patch(color='black', label='Extendable')
non_extendable_patch = mpatches.Patch(color='red', label='Fixed')
ax.legend(handles=[extendable_patch, non_extendable_patch], title = "Links/Lines log width")
/home/ivanra/.conda/envs/pypsa-china/lib/python3.12/site-packages/cartopy/mpl/feature_artist.py:144: UserWarning: facecolor will have no effect as it has been defined as "never".
  warnings.warn('facecolor will have no effect as it has been '
Out[81]:
<matplotlib.legend.Legend at 0x7fbbea642840>
No description has been provided for this image

Shadow prices & prices¶

In [82]:
from make_summary import make_summaries
summaries = make_summaries({("exp175-paper", PLANNING_YEAR):ntw_path})
/p/tmp/ivanra/PyPSA-China-PIK/workflow/scripts/make_summary.py:525: FutureWarning: DataFrame.groupby with axis=1 is deprecated. Do `frame.T.groupby(...)` without axis instead.
  .groupby(n.generators.loc[gens, "bus"], axis=1)
/p/tmp/ivanra/PyPSA-China-PIK/workflow/scripts/make_summary.py:525: FutureWarning: DataFrame.groupby with axis=1 is deprecated. Do `frame.T.groupby(...)` without axis instead.
  .groupby(n.generators.loc[gens, "bus"], axis=1)
/p/tmp/ivanra/PyPSA-China-PIK/workflow/scripts/make_summary.py:525: FutureWarning: DataFrame.groupby with axis=1 is deprecated. Do `frame.T.groupby(...)` without axis instead.
  .groupby(n.generators.loc[gens, "bus"], axis=1)
/p/tmp/ivanra/PyPSA-China-PIK/workflow/scripts/make_summary.py:525: FutureWarning: DataFrame.groupby with axis=1 is deprecated. Do `frame.T.groupby(...)` without axis instead.
  .groupby(n.generators.loc[gens, "bus"], axis=1)
/p/tmp/ivanra/PyPSA-China-PIK/workflow/scripts/make_summary.py:525: FutureWarning: DataFrame.groupby with axis=1 is deprecated. Do `frame.T.groupby(...)` without axis instead.
  .groupby(n.generators.loc[gens, "bus"], axis=1)
/p/tmp/ivanra/PyPSA-China-PIK/workflow/scripts/make_summary.py:525: FutureWarning: DataFrame.groupby with axis=1 is deprecated. Do `frame.T.groupby(...)` without axis instead.
  .groupby(n.generators.loc[gens, "bus"], axis=1)
/p/tmp/ivanra/PyPSA-China-PIK/workflow/scripts/make_summary.py:549: FutureWarning: DataFrame.groupby with axis=1 is deprecated. Do `frame.T.groupby(...)` without axis instead.
  .groupby(n.links.loc[links, "bus" + i], axis=1)
/p/tmp/ivanra/PyPSA-China-PIK/workflow/scripts/make_summary.py:549: FutureWarning: DataFrame.groupby with axis=1 is deprecated. Do `frame.T.groupby(...)` without axis instead.
  .groupby(n.links.loc[links, "bus" + i], axis=1)
/p/tmp/ivanra/PyPSA-China-PIK/workflow/scripts/make_summary.py:549: FutureWarning: DataFrame.groupby with axis=1 is deprecated. Do `frame.T.groupby(...)` without axis instead.
  .groupby(n.links.loc[links, "bus" + i], axis=1)
/p/tmp/ivanra/PyPSA-China-PIK/workflow/scripts/make_summary.py:549: FutureWarning: DataFrame.groupby with axis=1 is deprecated. Do `frame.T.groupby(...)` without axis instead.
  .groupby(n.links.loc[links, "bus" + i], axis=1)
/p/tmp/ivanra/PyPSA-China-PIK/workflow/scripts/make_summary.py:549: FutureWarning: DataFrame.groupby with axis=1 is deprecated. Do `frame.T.groupby(...)` without axis instead.
  .groupby(n.links.loc[links, "bus" + i], axis=1)
/p/tmp/ivanra/PyPSA-China-PIK/workflow/scripts/make_summary.py:549: FutureWarning: DataFrame.groupby with axis=1 is deprecated. Do `frame.T.groupby(...)` without axis instead.
  .groupby(n.links.loc[links, "bus" + i], axis=1)
/p/tmp/ivanra/PyPSA-China-PIK/workflow/scripts/make_summary.py:549: FutureWarning: DataFrame.groupby with axis=1 is deprecated. Do `frame.T.groupby(...)` without axis instead.
  .groupby(n.links.loc[links, "bus" + i], axis=1)
/p/tmp/ivanra/PyPSA-China-PIK/workflow/scripts/make_summary.py:556: RuntimeWarning: invalid value encountered in scalar divide
  market_values.at[tech, label] = revenue.sum().sum() / dispatch.sum().sum()
/p/tmp/ivanra/PyPSA-China-PIK/workflow/scripts/make_summary.py:549: FutureWarning: DataFrame.groupby with axis=1 is deprecated. Do `frame.T.groupby(...)` without axis instead.
  .groupby(n.links.loc[links, "bus" + i], axis=1)
/p/tmp/ivanra/PyPSA-China-PIK/workflow/scripts/make_summary.py:549: FutureWarning: DataFrame.groupby with axis=1 is deprecated. Do `frame.T.groupby(...)` without axis instead.
  .groupby(n.links.loc[links, "bus" + i], axis=1)
/p/tmp/ivanra/PyPSA-China-PIK/workflow/scripts/make_summary.py:549: FutureWarning: DataFrame.groupby with axis=1 is deprecated. Do `frame.T.groupby(...)` without axis instead.
  .groupby(n.links.loc[links, "bus" + i], axis=1)

CO2 emissions & Stores¶

In [83]:
n.global_constraints
Out[83]:
attribute type investment_period carrier_attribute sense constant mu
GlobalConstraint
In [84]:
fig, ax = plt.subplots()
# calc the t resolved CO2 emissions from generators
time_res_emissions = ((n.generators_t.p/ n.generators.efficiency)* n.generators.carrier.map(n.carriers.co2_emissions)).T.groupby(n.generators.carrier).sum()
colors = time_res_emissions.T.columns.map(tech_colors).fillna("lightgrey")
time_res_emissions.T.plot(ax =ax, color =  colors, legend = True)
Out[84]:
<Axes: xlabel='snapshot'>
No description has been provided for this image
In [85]:
gen_emissions = ((n.generators_t.p/ n.generators.efficiency)* n.generators.carrier.map(n.carriers.co2_emissions)).T.groupby(n.generators.carrier).sum().T.cumsum()
fig, ax = plt.subplots()
gen_emissions.where(gen_emissions>=0).dropna(axis=1, how="all").plot(ax=ax)
ax.set_ylabel("cum CO2 emissions [t]")
Out[85]:
Text(0, 0.5, 'cum CO2 emissions [t]')
No description has been provided for this image
In [86]:
try:
    fig, ax = plt.subplots()
    # calc the t resolved CO2 emissions from generators
    ((n.generators_t.p/ n.generators.efficiency)* n.generators.carrier.map(n.carriers.co2_emissions)).T.groupby(n.generators.carrier).sum().sum().T.cumsum().plot(ax =ax, label = "CO2 emissions (cum)", lw=4, c ="black", ls = "--")
    n.stores_t.e.T.groupby(n.stores.carrier).sum().loc[["CO2","H2", "CO2 capture", "gas", "biomass"]].T.plot(lw=4, cmap ="jet", ax =ax)
    ax.legend()
    ax.semilogy()
    ax.set_ylim(1e3,1e10)
    ax.set_ylabel("carrier stock")
except KeyError:
    print("Overnight model does not have CO2 capture or biomass stores, skipped")
Overnight model does not have CO2 capture or biomass stores, skipped
No description has been provided for this image
In [87]:
gas_stores = n.stores[n.stores.carrier.str.contains("gas")]
gas_stores_t = n.stores_t.e[n.stores_t.e.columns.intersection(gas_stores.index)]
gas_stores_t.sum(axis=1).plot()
Out[87]:
<Axes: xlabel='snapshot'>
No description has been provided for this image

CO2 capture¶

! beware the store is the difference :)

In [88]:
try:
    stores = n.stores_t.e.T.groupby(n.stores.carrier).sum()
    diff = stores.iloc[:, -1] -stores.iloc[:, 0]
    co2_cap = stores.iloc[:, -1].loc[["CO2 capture"]].sum()
    co2_cap
except KeyError:
    print("Overnight model does not have CO2 capture or biomass stores, skipped")
Overnight model does not have CO2 capture or biomass stores, skipped
In [89]:
n.stores_t.e.T.groupby(n.stores.carrier).sum().T.plot(lw=4, cmap ="jet")
Out[89]:
<Axes: xlabel='snapshot'>
No description has been provided for this image

Time series¶

Weekly energy balances¶

In [90]:
from plot_time_series import plot_energy_balance
In [91]:
ax = plot_energy_balance(n, config["plotting"], start_date=f"{PLANNING_YEAR}-01-01 01:00", end_date=f"{PLANNING_YEAR}-12-31 12:00:00")
ax.grid(axis='y')
ax.set_title("Electricity Balance")
Out[91]:
Text(0.5, 1.0, 'Electricity Balance')
No description has been provided for this image
In [92]:
ax = plot_energy_balance(n, config["plotting"], start_date=f"{PLANNING_YEAR}-07-24 00:00", end_date=f"{PLANNING_YEAR}-07-31 12:00:00")
ax.grid(axis='y')
ax.set_title("Electricity Balance")
Out[92]:
Text(0.5, 1.0, 'Electricity Balance')
No description has been provided for this image
In [93]:
ax = plot_energy_balance(n, config["plotting"], start_date=f"{PLANNING_YEAR}-03-31 21:00", end_date=f"{PLANNING_YEAR}-04-06 12:00:00")
ax.grid(axis='y')
ax.set_title("Electricity Balance")
Out[93]:
Text(0.5, 1.0, 'Electricity Balance')
No description has been provided for this image

price series¶

In [94]:
from constants import PROV_NAMES 
marginal_price_series = n.buses_t["marginal_price"][PROV_NAMES]
prov_max_price_series = marginal_price_series.T.max()
prov_min_price_series = marginal_price_series.T.min()
spread = marginal_price_series.T.max()-marginal_price_series.T.min()
load_ac = n.statistics.withdrawal(bus_carrier="AC", aggregate_time=False, groupby="location", comps = "Load")

regional_weighed_price = marginal_price_series.T.mul(load_ac).T.sum()/load_ac.T.sum()
order =regional_weighed_price.sort_values().index

weighed_prices = (marginal_price_series.T.mul(load_ac)/load_ac.sum()).sum()
ax = marginal_price_series[order].plot(alpha=0.5, lw=2, legend=False, cmap="plasma")
weighed_prices.plot(ax = ax, lw=3, c="black", label = "weighted price avg", alpha =0.8, ls = "--")
ax.legend(  loc='upper left', bbox_to_anchor=(1, 1), ncols=2)
Out[94]:
<matplotlib.legend.Legend at 0x7fbbdb6d74a0>
No description has been provided for this image
In [95]:
from _plot_utilities import find_weeks_of_interest
from constants import PROV_NAMES

winter_week, summer_week = find_weeks_of_interest(n, f"{PLANNING_YEAR}-04-01", f"{PLANNING_YEAR}-10-06")
marginal_price_series = n.buses_t["marginal_price"][PROV_NAMES]
prov_max_price_series = marginal_price_series.T.max()
prov_min_price_series = marginal_price_series.T.min()
spread = marginal_price_series.T.max()-marginal_price_series.T.min()
load_ac = n.statistics.withdrawal(bus_carrier="AC", aggregate_time=False, groupby="location", comps = "Load")
weighed_prices = (marginal_price_series.T.mul(load_ac)/load_ac.sum()).sum()
print("calculated prices")

ax = prov_min_price_series.loc[summer_week].plot(label = "Marginal price\n(least expensive province)")
prov_max_price_series.loc[summer_week].plot(label = "Marginal price\n(most expensive province)", ax =ax)
# spread.loc[summer_week].plot(label = "Price spread", ax =ax, ls= "--")
ax.set_ylabel("Price [€/MWh]")
# ax.vlines(winter_week[len(winter_week)//2], 0,prov_max_price_series.max()*1.2, color="black", ls = "--", label="peak event")
ax.vlines(summer_week[len(summer_week)//2], 0,prov_max_price_series.loc[summer_week].max()*1.2, color="black", ls ="--", label="peak event")

weighed_prices.loc[summer_week].plot(ax=ax, label ="national mean weighted price")
ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left', title="Legend")
calculated prices
Out[95]:
<matplotlib.legend.Legend at 0x7fbbd9c74e30>
No description has been provided for this image
In [96]:
ax = prov_min_price_series.plot(label = "Marginal price\n(least expensive province)", logy=True)
ax = prov_max_price_series.plot(label = "Marginal price\n(most expensive province)", logy=True)
ax.set_ylabel("Price [€/MWh]")
ax.vlines(winter_week[len(winter_week)//2], 0,prov_max_price_series.max()*1.2, color="black", ls = "--", label="peak event")
ax.vlines(summer_week[len(summer_week)//2], 0,prov_max_price_series.max()*1.2, color="black", ls ="--", label="peak event")

weighed_prices.plot(ax=ax, label ="national mean weighted price", logy=True)
ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left', title="Legend")
Out[96]:
<matplotlib.legend.Legend at 0x7fbbd8719df0>
No description has been provided for this image

Prices - balance overlay¶

In [97]:
ax = plot_energy_balance(n, config["plotting"], start_date=str(winter_week[0]), end_date=str(winter_week[-1]))
ax.grid(axis='y')
ax.set_title("Electricity Balance - worst week winter")
ax2 = ax.twinx()
prov_max_price_series.loc[winter_week].plot(ax=ax2, color="red", lw=2, ls = "--", label= "Most exp Prov")
weighed_prices.loc[winter_week].plot(ax=ax2, color = "blue", lw=3, ls = ":",label = "National weighted price")
fig = ax.get_figure()
ax2.set_ylabel("PriceEur/MWh", color="red")
ax2.tick_params(axis='y', labelcolor='red')
# Move the existing legend to the right
handles, labels = ax.get_legend_handles_labels()
handles2, labels2 = ax2.get_legend_handles_labels()
ax.legend(handles + handles2, labels + labels2, loc='center left', bbox_to_anchor=(1.1, 0.5))
/home/ivanra/.conda/envs/pypsa-china/lib/python3.12/site-packages/pandas/plotting/_matplotlib/core.py:1798: UserWarning: Attempting to set identical low and high ylims makes transformation singular; automatically expanding.
  ax.set_ylim(0, None)
Out[97]:
<matplotlib.legend.Legend at 0x7fbbd85c3f20>
No description has been provided for this image
In [98]:
ax = plot_energy_balance(n, config["plotting"], start_date=str(summer_week[0]), end_date=str(summer_week[-1]))
ax.grid(axis='y')
ax.set_title("Electricity Balance - worst week summer")
ax2 = ax.twinx()
prov_max_price_series.loc[summer_week].plot(ax=ax2, color="red", lw=2, ls = "--", label= "Most exp Prov")
weighed_prices.loc[summer_week].plot(ax=ax2, color = "blue", lw=3, ls = ":",label = "National weighted price")
fig = ax.get_figure()
ax2.set_ylabel("PriceEur/MWh", color="red")
ax2.tick_params(axis='y', labelcolor='red')
# Move the existing legend to the right
handles, labels = ax.get_legend_handles_labels()
handles2, labels2 = ax2.get_legend_handles_labels()
ax.legend(handles + handles2, labels + labels2, loc='center left', bbox_to_anchor=(1.1, 0.5))
/home/ivanra/.conda/envs/pypsa-china/lib/python3.12/site-packages/pandas/plotting/_matplotlib/core.py:1798: UserWarning: Attempting to set identical low and high ylims makes transformation singular; automatically expanding.
  ax.set_ylim(0, None)
Out[98]:
<matplotlib.legend.Legend at 0x7fbbd864f260>
No description has been provided for this image
In [99]:
if snakemake.config["heat_coupling"]:
    ax = plot_energy_balance(n, config["plotting"], start_date=f"{PLANNING_YEAR}-03-31 21:00", end_date=f"{PLANNING_YEAR}-09-06 12:00:00", bus_carrier="heat")
    ax.set_title("Heat balance")
    ax2 = ax.twinx()
    prov_max_price_series.loc[f"{PLANNING_YEAR}-03-31 21:00":f"{PLANNING_YEAR}-09-06 12:00:00"].plot(ax=ax2, color="red", lw=2, ls = "--", label= "Most exp Prov")
    weighed_prices.loc[f"{PLANNING_YEAR}-03-31 21:00":f"{PLANNING_YEAR}-09-06 12:00:00"].plot(ax=ax2, color = "blue", lw=3, ls = ":",label = "National weighted price")
    prov_min_price_series.loc[f"{PLANNING_YEAR}-03-31 21:00":f"{PLANNING_YEAR}-09-06 12:00:00"].plot(ax=ax2, color="green", lw=2, ls = "--", label= "Least exp Prov")

    fig = ax.get_figure()
    ax2.set_ylabel("PriceEur/MWh", color="red")
    ax2.tick_params(axis='y', labelcolor='red')
    ax2.set_ylim([0, prov_max_price_series.loc[f"{PLANNING_YEAR}-03-31 21:00":f"{PLANNING_YEAR}-09-06 12:00:00"].max()*3])
    # Move the existing legend to the right
    handles, labels = ax.get_legend_handles_labels()
    handles2, labels2 = ax2.get_legend_handles_labels()
    ax.legend(handles + handles2, labels + labels2, loc='center left', bbox_to_anchor=(1.1, 0.5))
In [100]:
if snakemake.config["heat_coupling"]:
    ax = plot_energy_balance(n, config["plotting"], start_date=f"{PLANNING_YEAR}-01-01 00:00", end_date=f"{PLANNING_YEAR}-12-31 23:00:00", bus_carrier="heat")
    ax.set_title("Heat balance")

LOAD DURATION CURVES¶

In [101]:
from plot_time_series import plot_residual_load_duration_curve, plot_load_duration_curve

fig, ax = plt.subplots(1, 2, figsize=(10, 6), sharey=True)
plot_load_duration_curve(n, carrier="AC", ax = ax[0])
plot_residual_load_duration_curve(n, ax = ax[1])
fig.tight_layout()
No description has been provided for this image
In [102]:
from plot_time_series import plot_regional_load_durations

plot_regional_load_durations(n, carrier="AC", cmap = "viridis")
Out[102]:
<Axes: xlabel='Hours', ylabel='Load [GW]'>
No description has been provided for this image
In [103]:
fix, ax = plt.subplots()
ds_AC = n.statistics.withdrawal(bus_carrier="AC", aggregate_time=False).loc[("Load", "-")]/1e3
ds_AC.plot(ax=ax, label="Electricity",  c="orange")
if snakemake.config["heat_coupling"]:
    ds_heat = n.statistics.withdrawal(bus_carrier="heat", aggregate_time=False).loc[("Load", "-")]/1e3
    ds_heat.plot(ax=ax, label="Heat", c = "blue")  
ax.legend()
ax.set_ylabel("EnergyDemand / GW")
Out[103]:
Text(0, 0.5, 'EnergyDemand / GW')
No description has been provided for this image

Power flows map¶

In [106]:
from _pypsa_helpers import get_location_and_carrier
transmission = n.statistics.transmission(
        bus_carrier="AC",
        groupby=get_location_and_carrier,
        aggregate_time=False,
    )
transm_tot = transmission.T.sum().reset_index()
transm_tot = transm_tot.set_index("location").rename(columns={0:"HV transmission"}).sort_values(by="HV transmission")
In [107]:
ax = (
    (transmission/1e6)
    .groupby("location")
    .sum().loc[transm_tot.index[::-1]]
    .T.loc[f"{PLANNING_YEAR}-07-20":f"{PLANNING_YEAR}-07-27"]
    .plot.area(stacked=True, cmap="magma_r")
)
ax.legend(loc="center left", bbox_to_anchor=(1, 0.5), title="Prov", ncols=2)
ax.set_ylabel("Transmission [TWh]")
Out[107]:
Text(0, 0.5, 'Transmission [TWh]')
No description has been provided for this image
In [122]:
ax = transm_tot.plot.bar(y="HV transmission")
ax.set_ylabel("HV Transmission / MWHr")
Out[122]:
Text(0, 0.5, 'HV Transmission / MWHr')
No description has been provided for this image

Plot cap factors time series¶

In [109]:
capacity_factors = n.statistics.capacity_factor(aggregate_time=False).loc[["Generator"]].droplevel(0).T
In [110]:
colors_lowered = {k.lower().rstrip():v for k,v in config["plotting"]["tech_colors"].items()}
cap_colors = {k: colors_lowered.get(k.lower().rstrip(), "lightgrey") for k in [c for c in capacity_factors.columns]}
axes = capacity_factors.dropna(axis=1, how="all").fillna(0).plot(subplots=True, figsize=(10,12), color= cap_colors)
for ax in axes:
    ax.set_ylim([0,1.1])
    ax.set_yticks([0,0.3, 0.6,1])
No description has been provided for this image
In [111]:
PLANNING_YEAR
Out[111]:
2035
In [112]:
weighting = n.snapshot_weightings.iloc[0,0]
In [113]:
region_rev = n.statistics.revenue(groupby = ["location", "carrier"], bus_carrier="AC", aggregate_time=False).fillna(0)
rev_regional = region_rev[(region_rev<0).all(axis=1)].groupby("location").sum().T
prod_reg = n.statistics.energy_balance(groupby = ["location", "carrier"], bus_carrier="AC", aggregate_time=False)*weighting
consum_regional = prod_reg.query("component=='Load'").groupby("location").sum().T
balance = prod_reg.groupby("location").sum().T
supp_regional = prod_reg.query("component!='Load'").groupby("location").sum().T
ax = (rev_regional/consum_regional).plot(cmap="viridis")
ax.set_ylabel("Expenditure/Withdrawal [€/MWh]")
ax.set_xlabel("Time")
ax.legend(loc='center left', bbox_to_anchor=(1, 0.5), ncols=2)
Out[113]:
<matplotlib.legend.Legend at 0x7fbbd81ba510>
No description has been provided for this image
In [114]:
import numpy as np

import matplotlib.pyplot as plt

data = pd.concat([supp_regional.sum(), -1*consum_regional.sum(), balance.sum()], axis=1)
data.columns = ["Supply", "Consum", "Balance"]
data = data.query("index != ''") # no location assigned
fig, ax = plt.subplots(figsize=(10, 6))
bar_width = 0.25
x = np.arange(len(data.index))
data = data/1e6
ax.bar(x - bar_width, data["Supply"], width=bar_width, label="Supply")
ax.bar(x, data["Consum"], width=bar_width, label="Consum")
ax.bar(x + bar_width, data["Balance"], width=bar_width, label="Balance")

ax.set_xticks(x)
ax.set_xticklabels(data.index, rotation =70, ha ='right', fontsize=11)
ax.legend()
ax.set_ylabel("Energy [TWh]", fontsize=12)
fig.tight_layout()
plt.show()
No description has been provided for this image
In [ ]:
max_val = max(supp_regional.max().max(), -1*consum_regional.max().max())

fig, axes = plt.subplots(6,6, sharex=True, sharey=True, figsize=(20,20))
norm_price =  n.buses_t["marginal_price"][PROV_NAMES].loc[f"{PLANNING_YEAR}-07-22":f"{PLANNING_YEAR}-07-28"]
norm_price/= norm_price.max()
norm_price*=(consum_regional*-1e-6).loc[f"{PLANNING_YEAR}-07-22":f"{PLANNING_YEAR}-07-28"].max().max()*0.8
consum_regional = consum_regional[PROV_NAMES]
for i,c in enumerate(consum_regional.columns):
    k, l = (i+4)//6, (i+4)%6
    (consum_regional[c]*-1e-3).loc[f"{PLANNING_YEAR}-07-22":f"{PLANNING_YEAR}-07-28"].plot(ax=axes[k,l])
    (supp_regional[c]*1e-3).loc[f"{PLANNING_YEAR}-07-22":f"{PLANNING_YEAR}-07-28"].plot(ax=axes[k,l], color = "orange", alpha = 0.5)
    axes[k,l].set_title(c)
    axes[k,l].set_ylim(0, max_val*1e-3)

    if l ==0:
        axes[k,l].set_ylabel("Withdrawal or supply [GWh]")
    if k==0:
        axes[k,l].set_xlabel("Time")
axes[0, 4].legend(["Withdrawal (blue)", "Supply (orange)"])
fig.tight_layout()
fig.subplots_adjust(hspace=0.15, wspace=0.1)
No description has been provided for this image
In [128]:
exports_regional =balance
max_val = balance.max().max()
fig, axes = plt.subplots(6,6, sharex=True, sharey=True, figsize=(20,20))
norm_price =  n.buses_t["marginal_price"][PROV_NAMES].loc[f"{PLANNING_YEAR}-12-22":f"{PLANNING_YEAR}-12-28"]
norm_price/= norm_price.max()
norm_price*= max_val*0.3e-3
for i,c in enumerate(consum_regional.columns):
    k, l = (i+4)//6, (i+4)%6
    exports = (exports_regional[c]*1e-3).loc[f"{PLANNING_YEAR}-12-22":f"{PLANNING_YEAR}-12-28"]
    exports.plot(ax=axes[k,l], color = "orange", alpha = 1)
    norm_price[c].plot(ax=axes[k,l], color = "green", alpha = 0.5, legend = False, ls ="--")

    axes[k,l].set_title(c)
    # axes[k,l].set_ylim( exports.min(), exports.max())
    axes[k,l].axhline(0, color="black", lw=1)
    if l ==0:
        axes[k,l].set_ylabel("Balance [GWh] \n Norm Price (green)")
    if k==0:
        axes[k,l].set_xlabel("Time")
axes[0, 4].legend(["Exports"])
fig.tight_layout()
fig.subplots_adjust(hspace=0.2, wspace=0.1)
No description has been provided for this image
In [117]:
from constants import PROV_NAMES 
ax = n.buses_t["marginal_price"][PROV_NAMES].loc[f"{PLANNING_YEAR}-07-22":f"{PLANNING_YEAR}-07-28"].plot(cmap="viridis",alpha =0.7, logy=True)
ax.set_ylabel("Marginal Price [€/MWh]")
ax.set_xlabel("Time")
ax.legend(loc='center left', bbox_to_anchor=(1, 0.5), title="Province", ncol=2)
Out[117]:
<matplotlib.legend.Legend at 0x7fbbd3b989e0>
No description has been provided for this image
In [118]:
rev_and_exp = n.statistics.revenue(bus_carrier="AC", aggregate_time=False).fillna(0).groupby(level=1).sum().T
prod = n.statistics.energy_balance(bus_carrier="AC", aggregate_time=False).fillna(0)
prod = prod[~(prod<=0).all(axis=1)]
prod=prod.clip(lower=1e-5).groupby(level=1).sum().T
hourly_price = rev_and_exp.clip(lower=0)/prod
hourly_price = hourly_price.loc[:,(hourly_price>0).all(axis=0)]
ax = hourly_price.plot(logy=False, cmap = "viridis")
ax.set_ylim([1, hourly_price.max().max()*1.2])
ax.set_ylabel("Revenue/Supply [€/MWh]")
ax.set_xlabel("Time")
ax.legend(loc='center left', bbox_to_anchor=(1, 0.5))

ax2 = (prod/1e6).plot(cmap="viridis", logy=False)
ax2.legend(loc='center left', bbox_to_anchor=(1, 0.5), title="Carrier", ncol=2)
ax2.set_ylabel("Supply [TWh]")
ax2.set_xlabel("Time")
ax2.legend(loc='center left', bbox_to_anchor=(1, 0.5))
Out[118]:
<matplotlib.legend.Legend at 0x7fbbd253bf20>
No description has been provided for this image
No description has been provided for this image
In [119]:
colors_lowered = {k.lower().rstrip():v for k,v in config["plotting"]["tech_colors"].items()}
cap_colors = {k: colors_lowered.get(k.lower().rstrip(), "lightgrey") for k in [c for c in capacity_factors.columns]}

# Apply rolling moving average filter
capacity_factors_smoothed = capacity_factors.rolling(window=24, min_periods=1).mean()

axes = capacity_factors_smoothed.dropna(axis=1, how="all").fillna(0).plot(subplots=True, figsize=(10,12), color= cap_colors)
for ax in axes:
    ax.set_ylim([0,1.1])
    ax.set_yticks([0,0.3, 0.6,1])
No description has been provided for this image

COSTS & prices¶

Price duration curve¶

In [120]:
n.buses.carrier[n.buses.carrier=="H2"].index.values
Out[120]:
array(['Anhui H2', 'Beijing H2', 'Chongqing H2', 'Fujian H2', 'Gansu H2',
       'Guangdong H2', 'Guangxi H2', 'Guizhou H2', 'Hainan H2',
       'Hebei H2', 'Heilongjiang H2', 'Henan H2', 'Hubei H2', 'Hunan H2',
       'InnerMongolia H2', 'Jiangsu H2', 'Jiangxi H2', 'Jilin H2',
       'Liaoning H2', 'Ningxia H2', 'Qinghai H2', 'Shaanxi H2',
       'Shandong H2', 'Shanghai H2', 'Shanxi H2', 'Sichuan H2',
       'Tianjin H2', 'Tibet H2', 'Xinjiang H2', 'Yunnan H2',
       'Zhejiang H2'], dtype=object)
In [129]:
ntwk_el_price = -1*n.statistics.revenue(bus_carrier="AC", aggregate_time=False, comps="Load")/n.statistics.withdrawal(bus_carrier="AC", aggregate_time=False, comps="Load")
ntwk_el_price.T.Load.sort_values(ascending=False).reset_index(drop=True).plot()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/p/tmp/ivanra/anaconda/ipykernel_2927777/276366657.py in ?()
      1 ntwk_el_price = -1*n.statistics.revenue(bus_carrier="AC", aggregate_time=False, comps="Load")/n.statistics.withdrawal(bus_carrier="AC", aggregate_time=False, comps="Load")
----> 2 ntwk_el_price.T.Load.sort_values(ascending=False).reset_index(drop=True).plot()

~/.conda/envs/pypsa-china/lib/python3.12/site-packages/pandas/core/generic.py in ?(self, name)
   6295             and name not in self._accessors
   6296             and self._info_axis._can_hold_identifiers_and_holds_name(name)
   6297         ):
   6298             return self[name]
-> 6299         return object.__getattribute__(self, name)

AttributeError: 'DataFrame' object has no attribute 'Load'
In [ ]:
carrier = "AC"
carrier_buses = n.buses.carrier[n.buses.carrier == carrier].index.values
nodal_prices = n.buses_t.marginal_price[carrier_buses]
In [131]:
import seaborn as sns

def plot_price_map(network: pypsa.Network, carrier = "AC", ax: object = None):

    if not ax:
        fig, ax = plt.subplots(figsize=(20, 8))
    else: 
        fig = ax.get_figure()

    carrier_buses = network.buses.carrier[network.buses.carrier == carrier].index.values
    nodal_prices = network.buses_t.marginal_price[carrier_buses]
    # Normalize nodal_prices with log transformation
    normalized_prices = np.log10(nodal_prices.clip(lower=0.1))

    # Create a heatmap of normalized nodal_prices
    sns.heatmap(normalized_prices.reset_index(drop=True).T, cmap="viridis", cbar_kws={'label': 'Log-Transformed Price [€/MWh]'}, ax=ax)

    # Customize the plot
    ax.set_title("Heatmap of Log-Transformed Nodal Prices")
    ax.set_xlabel("Time")
    ax.set_ylabel("Nodes")
    fig.tight_layout()

    return

plot_price_map(n)
No description has been provided for this image
In [ ]:
from plot_time_series import plot_price_duration_curve, plot_price_duration_by_node, plot_price_duration_by_carrier
In [137]:
plot_price_duration_by_node(n, carrier="AC")
Out[137]:
<Axes: >
No description has been provided for this image
In [132]:
def plot_load_duration_by_node(network: pypsa.Network, carrier: str = "AC", logy= True, y_lower = 1e-3, fig_shape=(8, 4)):
  
    if carrier == "AC":
        suffix = ""
    else:
        suffix = f" {carrier}"
    
    carrier_buses = n.buses.carrier[n.buses.carrier == carrier].index.values
    nodal_prices = n.buses_t.marginal_price[carrier_buses]

    if fig_shape[0] * fig_shape[1] < len(nodal_prices.columns):
        raise ValueError(
            f"Figure shape {fig_shape} is too small for {len(nodal_prices.columns)} regions. "
            + "Please increase the number of subplots."
        )
    fig, axes = plt.subplots(fig_shape[0], fig_shape[1], sharex=True, sharey=True, figsize=(12,12))

    # region by region sorting of prices 
    for i, region in enumerate(nodal_prices.columns):
        reg_pr = nodal_prices[region]
        reg_pr.sort_values(ascending=False).reset_index(drop=True).plot(ax=axes[i//4, i%fig_shape[1]], label=region)
        axes[i//4, i%fig_shape[1]].set_title(region, fontsize=10)
        if logy:
            axes[i//4, i%fig_shape[1]].semilogy()
        if y_lower:
            axes[i//4, i%fig_shape[1]].set_ylim(y_lower, reg_pr.max()*1.2)
        elif reg_pr.min() >1e-5 and not logy: 
            axes[i//4, i%fig_shape[1]].set_ylim(0, reg_pr.max()*1.2)
    fig.tight_layout(h_pad=0.2, w_pad=0.2)
    for ax in axes.flat:
        # Remove all x-tick labels except the largest value
        xticks = ax.get_xticks()
        if len(xticks) > 0:
            ax.set_xticks([xticks[0], xticks[-1]])
            ax.set_xticklabels([f"{xticks[0]:.0f}", f"{xticks[-1]:.0f}"])

    return ax

plot_load_duration_by_node(n, carrier="AC", logy=True, fig_shape=(8, 4))
Out[132]:
<Axes: >
No description has been provided for this image
In [133]:
plot_load_duration_by_node(n, carrier="H2", logy=False, fig_shape=(8, 4), y_lower=0)
Out[133]:
<Axes: >
No description has been provided for this image
In [ ]:
from plot_summary_all import plot_prices
paths = [os.path.join(results_dir, "summary", f"ntwk_{yr}") for yr in snakemake.config["scenario"]["planning_horizons"]]
data_paths = {
        "energy": [os.path.join(p, "energy.csv") for p in paths],
        "costs": [os.path.join(p, "costs.csv") for p in paths],
        "co2_price": [os.path.join(p, "metrics.csv") for p in paths],
        "time_averaged_prices": [os.path.join(p, "time_averaged_prices.csv") for p in paths],
        "weighted_prices": [os.path.join(p, "weighted_prices.csv") for p in paths],
        "co2_balance": [os.path.join(p, "co2_balance.csv") for p in paths],
        "energy_supply": [os.path.join(p, "supply_energy.csv") for p in paths],
        "capacity": [os.path.join(p, "capacities.csv") for p in paths],
    }

fig, ax = plt.subplots()
plot_prices(
    data_paths["time_averaged_prices"],
    config["plotting"],
    fig_name=None,
    ax=ax
)

fig, ax = plt.subplots()
plot_prices(
    data_paths["weighted_prices"],
    config["plotting"],
    fig_name=None,
    ax=ax,
)
# ax.semilogy()
No description has been provided for this image
No description has been provided for this image
In [ ]:
stores_inflow = n.stores_t.p.sum()
h2_stores= stores_inflow.loc[[c for c in stores_inflow.index if c.find("H2 Store")!=-1]]
h2_stores_inflow = h2_stores.where(h2_stores>0,0).sum()
h2_stores_outflow = h2_stores.where(h2_stores<0,0).sum()

STATS¶

In [ ]:
n.statistics.optimal_capacity( groupby = pypsa.statistics.get_carrier_and_bus_carrier, nice_names=True)
Out[ ]:
component    carrier               bus_carrier    
Store        Battery Storage       Battery Storage    1.110708e+07
             H2                    H2                 3.332356e+02
             gas                   gas                3.667870e+08
             stations              stations           4.535627e+07
Link         AC                    AC                 1.731581e+06
             Battery Storage       AC                 1.989285e+06
             H2 Electrolysis       AC                 1.664580e+00
             H2 Fuel Cell          H2                 6.577840e+00
             H2 pipeline           H2                 5.202289e+01
             Hydroelectricity      stations           1.403220e+05
             Open-Cycle Gas        gas                1.210529e+05
             battery discharger    Battery Storage    2.030305e+06
             stations              stations           2.483820e+08
StorageUnit  Pumped Hydro Storage  AC                 3.639000e+04
Generator    Coal Power Plant      AC                 7.889498e+05
             Hydroelectricity      AC                 2.253780e+05
             Offshore Wind         AC                 7.750480e+03
             Onshore Wind          AC                 8.109340e+04
             Solar                 AC                 1.099864e+07
             gas                   gas                1.222053e+08
             hydro_inflow          stations           5.408039e+06
             nuclear               AC                 5.464707e+04
dtype: float64
In [ ]:
def get_solver_tolerance(config: dict, tol_name = "BarConvTol")->float:
    """get the solver tolerance from the config 

    Args:
        config (dict): the config
        tol_name (str): the name of the tolerance option. Defaults to "BarConvTol"

    Returns:
        float: the value
    """    
    solver_opts = config["solving"]["solver"]["options"]
    return config["solving"]["solver_options"][solver_opts][tol_name]

def find_numerical_zeros(n, config, tolerance_name = "BarConvTol")->list:
    """
    Identify numerical zeros in the network's optimization results.

    This function checks for numerical zeros in the network's optimization results, 
    such as link capacities or weighted prices, based on a specified solver tolerance.

    Args:
        n (pypsa.Network): The PyPSA network object containing optimization results.
        config (dict): Configuration dictionary containing solver options.
        tolerance_name (str): The name of the solver tolerance option to use. Defaults to "BarConvTol".

    Returns:
        list: A list of items (e.g., links or buses) where numerical zeros are detected.
    """
 
    tol = get_solver_tolerance(config, tolerance_name)
    threshold = n.objective*float(tol)
    
    costs = pd.concat([n.statistics.expanded_capex(), n.statistics.opex()],axis=1)
    return costs.fillna(0).sum(axis=1).loc[costs.sum(axis=1) < threshold].index
    
num_zeros = find_numerical_zeros(n, config, tolerance_name = "BarConvTol")
In [ ]:
bus_carrier = "AC"
n.loads.carrier = "load"
n.carriers.loc["load", ["nice_name", "color"]] = "Load", "#110d63"
colors = n.carriers.set_index("nice_name").color.where(
    lambda s: s != "", "lightgrey"
)

def rename_index(ds):
    specific = ds.index.map(lambda x: f"{x[1]}\n({x[0]})")
    generic = ds.index.get_level_values("carrier")
    duplicated = generic.duplicated(keep=False)
    index = specific.where(duplicated, generic)
    return ds.set_axis(index)

def plot_static_per_carrier(ds, ax, drop_zero=True):
    if drop_zero:
        ds = ds[ds != 0]
    ds = ds.dropna()
    c = colors[ds.index.get_level_values("carrier")]
    ds = ds.pipe(rename_index)
    label = f"{ds.attrs['name']} [{ds.attrs['unit']}]"
    ds.plot.barh(color=c.values, xlabel=label, ax=ax)
    ax.grid(axis="y")

fig, ax = plt.subplots()
ds = n.statistics.capacity_factor(bus_carrier=bus_carrier).dropna()
plot_static_per_carrier(ds, ax)
plt.show()

fig, ax = plt.subplots()
ds = n.statistics.installed_capacity(bus_carrier=bus_carrier).dropna()
if "Line" in ds.index:
    ds = ds.drop("Line")
ds = ds.drop(("Generator", "Load"), errors="ignore")
ds = ds / 1e3
ds.attrs["unit"] = "GW"
plot_static_per_carrier(ds.abs(), ax)
plt.show()

fig, ax = plt.subplots()
ds = n.statistics.optimal_capacity(bus_carrier=bus_carrier)
if "Line" in ds.index:
    ds = ds.drop("Line")
ds = ds.drop(("Generator", "Load"), errors="ignore")
ds = ds.abs() / 1e3
ds.attrs["unit"] = "GW"
plot_static_per_carrier(ds, ax)
plt.show()

fig, ax = plt.subplots()
ds = n.statistics.capex(bus_carrier=bus_carrier)
plot_static_per_carrier(ds, ax)
plt.show()

fig, ax = plt.subplots()
ds = n.statistics.opex(bus_carrier=bus_carrier)
plot_static_per_carrier(ds, ax)
plt.show()

fig, ax = plt.subplots()
ds = n.statistics.curtailment(bus_carrier=bus_carrier)
plot_static_per_carrier(ds, ax)
plt.show()

fig, ax = plt.subplots()
ds = n.statistics.supply(bus_carrier=bus_carrier)
if "Line" in ds.index:
    ds = ds.drop("Line")
ds = ds / 1e6
ds.attrs["unit"] = "TWh"
plot_static_per_carrier(ds, ax)
plt.show()

if snakemake.config["heat_coupling"]:
    fig, ax = plt.subplots()
    ds = n.statistics.supply(bus_carrier="heat")
    if "Line" in ds.index:
        ds = ds.drop("Line")
    ds = ds / 1e6
    ds.attrs["unit"] = "TWh"
    plot_static_per_carrier(ds, ax)
    plt.show()

fig, ax = plt.subplots()
ds = n.statistics.withdrawal(bus_carrier=bus_carrier)
if "Line" in ds.index:
    ds = ds.drop("Line")
ds = ds / -1e6
ds.attrs["unit"] = "TWh"
plot_static_per_carrier(ds, ax)
plt.show()

fig, ax = plt.subplots()
ds = n.statistics.market_value(bus_carrier=bus_carrier)
plot_static_per_carrier(ds, ax)
plt.show()
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
In [ ]:
def calc_lcoe(n: pypsa.Network, grouper = pypsa.statistics.get_carrier_and_bus_carrier, **kwargs)->pd.DataFrame:
    """calculate the LCOE for the network: (capex+opex)/supply.

    Args:
        n (pypsa.Network): the network for which LCOE is to be calaculated
        grouper (function | list, optional): function to group the data in network.statistics.
                Overwritten if groupby is passed in kwargs.
                Defaults to pypsa.statistics.get_carrier_and_bus_carrier. 
        **kwargs: other arguments to be passed to network.statistics
    Returns:
        pd.DataFrame: The LCOE for the network  with or without brownfield CAPEX. MV and delta

    """    
    if "groupby" in kwargs:
        grouper = kwargs.pop("groupby")

    rev= n.statistics.revenue(groupby=grouper,**kwargs)
    opex = n.statistics.opex(groupby=grouper,**kwargs)
    capex = n.statistics.expanded_capex(groupby=grouper,**kwargs)
    tot_capex = n.statistics.capex(groupby=grouper,**kwargs)
    supply = n.statistics.supply(groupby=grouper,**kwargs)

    profits = pd.concat([opex, capex, tot_capex, rev, supply], axis=1,  keys = ["OPEX", "CAPEX", "CAPEX_wBROWN", "Revenue", "supply"]).fillna(0)
    profits["rev-costs"]=profits.apply(lambda row: row.Revenue-row.CAPEX-row.OPEX, axis=1)
    profits["LCOE"] = profits.apply(lambda row: (row.CAPEX+row.OPEX)/row.supply, axis=1)
    profits["LCOE_wbrownfield"] = profits.apply(lambda row: (row.CAPEX_wBROWN+row.OPEX)/row.supply, axis=1)
    profits["MV"] = profits.apply(lambda row: row.Revenue/row.supply, axis=1)
    profits["profit_pu"]=profits["rev-costs"]/profits.supply
    profits.sort_values("profit_pu", ascending=False, inplace=True)
    
    return profits[profits.supply>0]
In [ ]:
 
In [ ]:
rev_costs = calc_lcoe(n, groupby=None)
rev_costs = calc_lcoe(n, groupby=None)
ds = rev_costs["LCOE"]
ds.attrs={"name":"LCOE", "unit":"€/MWh"}
fig, ax = plt.subplots()
plot_static_per_carrier(ds[~ds.index.isin(num_zeros)], ax)
plt.show()

rev_costs = calc_lcoe(n, groupby=None)
ds = rev_costs["profit_pu"]
ds.attrs={"name":"MV - LCOE", "unit":"€/MWh"}
fig, ax = plt.subplots()
plot_static_per_carrier(ds[~ds.index.isin(num_zeros)], ax)
plt.show()
WARNING:pypsa.statistics:Network has multiple bus carriers which are aggregated together. To separate bus carriers set `bus_carrier` or use groupers like `get_carrier_and_bus_carrier`.
/tmp/ipykernel_1698823/2397917093.py:25: RuntimeWarning:

invalid value encountered in scalar divide

/tmp/ipykernel_1698823/2397917093.py:26: RuntimeWarning:

invalid value encountered in scalar divide

/tmp/ipykernel_1698823/2397917093.py:27: RuntimeWarning:

divide by zero encountered in scalar divide

WARNING:pypsa.statistics:Network has multiple bus carriers which are aggregated together. To separate bus carriers set `bus_carrier` or use groupers like `get_carrier_and_bus_carrier`.
/tmp/ipykernel_1698823/2397917093.py:25: RuntimeWarning:

invalid value encountered in scalar divide

/tmp/ipykernel_1698823/2397917093.py:26: RuntimeWarning:

invalid value encountered in scalar divide

/tmp/ipykernel_1698823/2397917093.py:27: RuntimeWarning:

divide by zero encountered in scalar divide

No description has been provided for this image
WARNING:pypsa.statistics:Network has multiple bus carriers which are aggregated together. To separate bus carriers set `bus_carrier` or use groupers like `get_carrier_and_bus_carrier`.
/tmp/ipykernel_1698823/2397917093.py:25: RuntimeWarning:

invalid value encountered in scalar divide

/tmp/ipykernel_1698823/2397917093.py:26: RuntimeWarning:

invalid value encountered in scalar divide

/tmp/ipykernel_1698823/2397917093.py:27: RuntimeWarning:

divide by zero encountered in scalar divide

No description has been provided for this image

Network topology¶

irrelevant as don't have lines

In [ ]:
n.determine_network_topology()
n.sub_networks["n_branches"] = [
    len(sn.branches()) for sn in n.sub_networks.obj
]
n.sub_networks["n_buses"] = [len(sn.buses()) for sn in n.sub_networks.obj]

CO2 costs¶

In [ ]:
if "emission_prices" in n.meta:
    em_price_gas = float(n.statistics.supply(comps="Generator")["gas"]*n.meta["emission_prices"]["co2"]*0.2/0.43)
    print(f"{em_price_gas:.2e}")
n.global_constraints
WARNING:pypsa.statistics:Network has multiple bus carriers which are aggregated together. To separate bus carriers set `bus_carrier` or use groupers like `get_carrier_and_bus_carrier`.
2.61e+08
Out[ ]:
attribute type investment_period carrier_attribute sense constant mu
GlobalConstraint
In [ ]:
costs.rename(columns={"Unnamed: 0":"expend", "Unnamed: 1": "carrier"}, inplace=True)
costs_ = costs.set_index(["expend", "carrier"]).T
costs_.T.groupby("expend").sum().T.sort_index().plot(kind="bar", stacked=True)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[59], line 1
----> 1 costs.rename(columns={"Unnamed: 0":"expend", "Unnamed: 1": "carrier"}, inplace=True)
      2 costs_ = costs.set_index(["expend", "carrier"]).T
      3 costs_.T.groupby("expend").sum().T.sort_index().plot(kind="bar", stacked=True)

NameError: name 'costs' is not defined
In [ ]:
costs_s = costs_.T.sum()
dt = 5
costs_s.index = costs_s.index.astype(int)
co2_costs = co2.set_index("Year")["costs bnE"]
costs_df = pd.DataFrame({'Costs pw bn eur': costs_s, 'CO2 Costs bn eur': co2_costs})
costs_df["Costs no co2 bn eur"] = costs_df["Costs pw bn eur"] - costs_df["CO2 Costs bn eur"]
costs_df["discount"]=costs_df.apply(lambda x: 1.02**(x.name-min(costs_df.index)), axis =1)
discounted_costs = costs_df.apply(lambda x: x/x.discount, axis=1)
discounted_costs, costs_df.apply(lambda x: x.sum()*5 ), discounted_costs.apply(lambda x: x.sum()*5 )
Out[ ]:
(      Costs pw bn eur  CO2 Costs bn eur  Costs no co2 bn eur  discount
 2020       426.415207         54.897743           371.517464       1.0
 2025       459.913593         72.058113           387.855480       1.0
 2030       450.013123         55.538548           394.474575       1.0
 2035       428.357785         35.076815           393.280970       1.0
 2040       397.500409         19.424611           378.075799       1.0
 2045       375.332390         13.015016           362.317373       1.0
 2050       353.459437          8.496730           344.962707       1.0
 2055       368.593595         18.899338           349.694256       1.0
 2060       369.697213          0.000000           369.697213       1.0,
 Costs pw bn eur        27297.031714
 CO2 Costs bn eur        1763.846907
 Costs no co2 bn eur    25533.184807
 discount                  69.073939
 dtype: float64,
 Costs pw bn eur        18146.413756
 CO2 Costs bn eur        1387.034575
 Costs no co2 bn eur    16759.379181
 discount                  45.000000
 dtype: float64)
In [ ]:
discounted_costs["Costs pw bn eur"].plot(kind="bar", stacked=True, color="blue", label="Costs pw bn eur")
Out[ ]:
<Axes: >
No description has been provided for this image
In [ ]:
ax = costs_.T.unstack(level=0).T.sort_index().plot(
    kind='bar', 
    stacked=True, 
    figsize=(12, 6), 
    width=0.8, 
    legend=True)


# Customize the legend with a dictionary, including bbox_to_anchor
legend_kw = {
    'title': 'Carrier',          # Title of the legend
    'loc': 'upper left',              # Legend location (usually 'upper left' or 'best')
    'fontsize': 'small',              # Font size for the legend
    'frameon': False,                 # Remove the legend's frame
    'ncol': 2,                        # Number of columns in the legend
    'bbox_to_anchor': (1.05, 1),      # Adjust the legend's position (x, y)
    'borderaxespad': 0.5              # Padding between the legend and plot
}

# Apply the legend customization
ax.legend(**legend_kw)
Out[ ]:
<matplotlib.legend.Legend at 0x7f0e63723c20>
No description has been provided for this image

Validation¶

In [ ]:
import seaborn as sns
In [ ]:
capex_ = n.statistics.expanded_capex().sum()
installed_capex_ = n.statistics.installed_capex().sum()
opex_ = n.statistics.opex().sum()
costs_ = pd.DataFrame(
    {
        "OPEX": opex_,
        "Installed CAPEX": installed_capex_,
        "CAPEX": capex_,
        "total": capex_ + installed_capex_ + opex_,
    }, index = ["Costs"]
).stack()
revenue_ = n.statistics.revenue(comps="Load", bus_carrier="AC").sum()
costs_.loc[("Revenue", "AC")]=revenue_*-1
all_ = pd.concat({f"{PLANNING_YEAR} budget":costs_}, names=["CO2 control"])
all_.index.names = ["CO2 control", "Cat", "Type"]
all_ = pd.DataFrame(all_, columns = ["Value"])


sns.catplot(all_,x="Cat", y="Value",  hue="Type", col = "CO2 control", dodge=True, kind="bar", alpha =0.8)
Out[ ]:
<seaborn.axisgrid.FacetGrid at 0x7f0e588c20c0>
No description has been provided for this image
In [ ]:
rev= n.statistics.revenue(groupby=pypsa.statistics.get_carrier_and_bus_carrier)
opex = n.statistics.opex(groupby=pypsa.statistics.get_carrier_and_bus_carrier)
capex = n.statistics.expanded_capex(groupby=pypsa.statistics.get_carrier_and_bus_carrier)
supply = n.statistics.supply(groupby=pypsa.statistics.get_carrier_and_bus_carrier)
index = rev.index.union(opex.index).union(capex.index)
rents = pd.concat([opex, capex, rev, supply], axis=1,  keys = ["OPEX", "CAPEX", "Revenue", "supply"]).fillna(0)
rents["delta"]=rents.apply(lambda row: row.Revenue-row.CAPEX-row.OPEX, axis=1)
rents["delta_pu"]=rents.delta/rents.supply
rents.sort_values("delta_pu", ascending=False, inplace=True)
In [ ]:
supply
Out[ ]:
component    carrier               bus_carrier    
Generator    Coal Power Plant      AC                 1.852240e+04
             Offshore Wind         AC                 1.129338e+09
             Onshore Wind          AC                 4.365634e+08
             Solar                 AC                 1.216664e+10
             gas                   gas                1.491579e+09
             hydro_inflow          stations           3.517783e+08
             nuclear               AC                 1.059834e+09
Link         AC                    AC                 6.748333e+09
             Battery Storage       Battery Storage    6.497103e+09
             H2 Electrolysis       H2                 6.189096e+07
             H2 Fuel Cell          AC                 2.978052e+07
             H2 pipeline           H2                 1.746955e+08
             Hydroelectricity      AC                 4.797237e+08
                                   stations           2.684737e+08
             Open-Cycle Gas        AC                 6.413986e+08
             Sabatier              gas                4.756363e+04
             battery discharger    AC                 6.365834e+09
             stations              stations           3.586407e+07
StorageUnit  Pumped Hydro Storage  AC                 4.595525e+07
Store        Battery Storage       Battery Storage    6.138904e+09
             H2                    H2                 5.333911e+07
             gas                   gas                1.075529e+09
             stations              stations           3.520498e+08
dtype: float64
In [ ]:
rev
Out[ ]:
component    carrier               bus_carrier    
Generator    Coal Power Plant      AC                 1.937514e+06
             Offshore Wind         AC                 5.746152e+10
             Onshore Wind          AC                 2.927233e+10
             Solar                 AC                 3.365797e+11
             gas                   gas                6.775035e+10
             hydro_inflow          stations           9.585767e+09
             nuclear               AC                 6.935263e+10
Link         AC                    AC                 6.998466e+10
             Battery Storage       AC                -1.565939e+11
                                   Battery Storage    1.874158e+11
             H2 Electrolysis       AC                -1.388081e+08
                                   H2                 6.231078e+08
             H2 Fuel Cell          AC                 2.918551e+09
                                   H2                -2.052169e+09
             H2 pipeline           AC                -3.677823e+07
                                   H2                 3.823994e+08
             Hydroelectricity      AC                 3.831391e+10
                                   stations          -1.515727e+10
             Open-Cycle Gas        AC                 8.551149e+10
                                   gas               -6.775238e+10
             Sabatier              H2                -8.002653e+05
                                   gas                2.111894e+06
             battery discharger    AC                 2.940412e+11
                                   Battery Storage   -2.940339e+11
             stations              stations          -2.021822e+06
StorageUnit  Pumped Hydro Storage  AC                 2.596869e+09
Store        Battery Storage       Battery Storage    1.066181e+11
             H2                    H2                 1.047455e+09
             gas                   gas                8.199906e+03
             stations              stations           5.573505e+09
Load         Load                  AC                -8.292652e+11
dtype: float64
In [ ]:
rents[rents.supply>1e5].droplevel(0).sort_values("delta_pu", ascending=False).plot.bar( y="delta_pu", stacked=True, figsize=(10, 6), ylabel="Rev-Costs Eur/MWh")
Out[ ]:
<Axes: xlabel='carrier,bus_carrier', ylabel='Rev-Costs Eur/MWh'>
No description has been provided for this image
In [ ]:
n.links[n.links.carrier=="gas OCGT"]
Out[ ]:
bus0 bus1 type carrier efficiency active build_year lifetime p_nom p_nom_mod ... down_time_before ramp_limit_up ramp_limit_down ramp_limit_start_up ramp_limit_shut_down p_nom_opt bus2 efficiency2 location plottable
Link
Anhui OCGT Anhui gas Anhui gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.210749e-08 1.0 Anhui True
Beijing OCGT Beijing gas Beijing gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.219836e-08 1.0 Beijing True
Chongqing OCGT Chongqing gas Chongqing gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.180835e-08 1.0 Chongqing True
Fujian OCGT Fujian gas Fujian gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.135034e-08 1.0 Fujian True
Gansu OCGT Gansu gas Gansu gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.268867e-08 1.0 Gansu True
Guangdong OCGT Guangdong gas Guangdong gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.160233e-08 1.0 Guangdong True
Guangxi OCGT Guangxi gas Guangxi gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 9.593560e-09 1.0 Guangxi True
Guizhou OCGT Guizhou gas Guizhou gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.133800e-08 1.0 Guizhou True
Hainan OCGT Hainan gas Hainan gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.128516e-08 1.0 Hainan True
Hebei OCGT Hebei gas Hebei gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.281898e-08 1.0 Hebei True
Heilongjiang OCGT Heilongjiang gas Heilongjiang gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.200628e-08 1.0 Heilongjiang True
Henan OCGT Henan gas Henan gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.225126e-08 1.0 Henan True
Hubei OCGT Hubei gas Hubei gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.137650e-08 1.0 Hubei True
Hunan OCGT Hunan gas Hunan gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.307639e-08 1.0 Hunan True
InnerMongolia OCGT InnerMongolia gas InnerMongolia gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.214625e-08 1.0 InnerMongolia True
Jiangsu OCGT Jiangsu gas Jiangsu gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.074345e-08 1.0 Jiangsu True
Jiangxi OCGT Jiangxi gas Jiangxi gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.296391e-08 1.0 Jiangxi True
Jilin OCGT Jilin gas Jilin gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.237052e-08 1.0 Jilin True
Liaoning OCGT Liaoning gas Liaoning gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.239674e-08 1.0 Liaoning True
Ningxia OCGT Ningxia gas Ningxia gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.192159e-08 1.0 Ningxia True
Qinghai OCGT Qinghai gas Qinghai gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.238705e-08 1.0 Qinghai True
Shaanxi OCGT Shaanxi gas Shaanxi gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.292193e-08 1.0 Shaanxi True
Shandong OCGT Shandong gas Shandong gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.279889e-08 1.0 Shandong True
Shanghai OCGT Shanghai gas Shanghai gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.178126e-08 1.0 Shanghai True
Shanxi OCGT Shanxi gas Shanxi gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.178651e-08 1.0 Shanxi True
Sichuan OCGT Sichuan gas Sichuan gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.208614e-08 1.0 Sichuan True
Tianjin OCGT Tianjin gas Tianjin gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.196493e-08 1.0 Tianjin True
Tibet OCGT Tibet gas Tibet gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 7.946448e+02 1.0 Tibet True
Xinjiang OCGT Xinjiang gas Xinjiang gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.039274e-08 1.0 Xinjiang True
Yunnan OCGT Yunnan gas Yunnan gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.160114e-08 1.0 Yunnan True
Zhejiang OCGT Zhejiang gas Zhejiang gas OCGT 0.4 True 0 25.0 0.0 0.0 ... 0 NaN NaN 1.0 1.0 1.102403e-08 1.0 Zhejiang True
Jiangsu OCGT-2000.0 Jiangsu gas Jiangsu OCGT gas OCGT 0.4 True 2000 25.0 127.5 0.0 ... 0 NaN NaN 1.0 1.0 1.275000e+02 1.0 Jiangsu False
Tianjin OCGT-2000.0 Tianjin gas Tianjin OCGT gas OCGT 0.4 True 2000 25.0 135.0 0.0 ... 0 NaN NaN 1.0 1.0 1.350000e+02 1.0 Tianjin False
Zhejiang OCGT-2000.0 Zhejiang gas Zhejiang OCGT gas OCGT 0.4 True 2000 25.0 1715.0 0.0 ... 0 NaN NaN 1.0 1.0 1.715000e+03 1.0 Zhejiang False
Guangdong OCGT-2005.0 Guangdong gas Guangdong OCGT gas OCGT 0.4 True 2005 25.0 4187.5 0.0 ... 0 NaN NaN 1.0 1.0 4.187500e+03 1.0 Guangdong False
Hainan OCGT-2005.0 Hainan gas Hainan OCGT gas OCGT 0.4 True 2005 25.0 1475.0 0.0 ... 0 NaN NaN 1.0 1.0 1.475000e+03 1.0 Hainan False
Jiangsu OCGT-2005.0 Jiangsu gas Jiangsu OCGT gas OCGT 0.4 True 2005 25.0 2925.0 0.0 ... 0 NaN NaN 1.0 1.0 2.925000e+03 1.0 Jiangsu False
Shanghai OCGT-2005.0 Shanghai gas Shanghai OCGT gas OCGT 0.4 True 2005 25.0 1800.0 0.0 ... 0 NaN NaN 1.0 1.0 1.800000e+03 1.0 Shanghai False
Zhejiang OCGT-2005.0 Zhejiang gas Zhejiang OCGT gas OCGT 0.4 True 2005 25.0 3670.0 0.0 ... 0 NaN NaN 1.0 1.0 3.670000e+03 1.0 Zhejiang False
Fujian OCGT-2010.0 Fujian gas Fujian OCGT gas OCGT 0.4 True 2010 25.0 9750.0 0.0 ... 0 NaN NaN 1.0 1.0 9.750000e+03 1.0 Fujian False
Guangdong OCGT-2010.0 Guangdong gas Guangdong OCGT gas OCGT 0.4 True 2010 25.0 8250.0 0.0 ... 0 NaN NaN 1.0 1.0 8.250000e+03 1.0 Guangdong False
Henan OCGT-2010.0 Henan gas Henan OCGT gas OCGT 0.4 True 2010 25.0 3900.0 0.0 ... 0 NaN NaN 1.0 1.0 3.900000e+03 1.0 Henan False
InnerMongolia OCGT-2010.0 InnerMongolia gas InnerMongolia OCGT gas OCGT 0.4 True 2010 25.0 750.0 0.0 ... 0 NaN NaN 1.0 1.0 7.500000e+02 1.0 InnerMongolia False
Jiangsu OCGT-2010.0 Jiangsu gas Jiangsu OCGT gas OCGT 0.4 True 2010 25.0 975.0 0.0 ... 0 NaN NaN 1.0 1.0 9.750000e+02 1.0 Jiangsu False
Shanghai OCGT-2010.0 Shanghai gas Shanghai OCGT gas OCGT 0.4 True 2010 25.0 3000.0 0.0 ... 0 NaN NaN 1.0 1.0 3.000000e+03 1.0 Shanghai False
Zhejiang OCGT-2010.0 Zhejiang gas Zhejiang OCGT gas OCGT 0.4 True 2010 25.0 6392.5 0.0 ... 0 NaN NaN 1.0 1.0 6.392500e+03 1.0 Zhejiang False
Guangdong OCGT-2015.0 Guangdong gas Guangdong OCGT gas OCGT 0.4 True 2015 25.0 1260.0 0.0 ... 0 NaN NaN 1.0 1.0 1.260000e+03 1.0 Guangdong False
Jiangsu OCGT-2015.0 Jiangsu gas Jiangsu OCGT gas OCGT 0.4 True 2015 25.0 2375.0 0.0 ... 0 NaN NaN 1.0 1.0 2.375000e+03 1.0 Jiangsu False
Shanghai OCGT-2015.0 Shanghai gas Shanghai OCGT gas OCGT 0.4 True 2015 25.0 5000.0 0.0 ... 0 NaN NaN 1.0 1.0 5.000000e+03 1.0 Shanghai False
Sichuan OCGT-2015.0 Sichuan gas Sichuan OCGT gas OCGT 0.4 True 2015 25.0 1750.0 0.0 ... 0 NaN NaN 1.0 1.0 1.750000e+03 1.0 Sichuan False
Tibet OCGT-2015.0 Tibet gas Tibet OCGT gas OCGT 0.4 True 2015 25.0 450.0 0.0 ... 0 NaN NaN 1.0 1.0 4.500000e+02 1.0 Tibet False
Hainan OCGT-2020.0 Hainan gas Hainan OCGT gas OCGT 0.4 True 2020 25.0 2300.0 0.0 ... 0 NaN NaN 1.0 1.0 2.300000e+03 1.0 Hainan False
Jiangsu OCGT-2020.0 Jiangsu gas Jiangsu OCGT gas OCGT 0.4 True 2020 25.0 2000.0 0.0 ... 0 NaN NaN 1.0 1.0 2.000000e+03 1.0 Jiangsu False
Shanghai OCGT-2020.0 Shanghai gas Shanghai OCGT gas OCGT 0.4 True 2020 25.0 2121.0 0.0 ... 0 NaN NaN 1.0 1.0 2.121000e+03 1.0 Shanghai False
Shanxi OCGT-2020.0 Shanxi gas Shanxi OCGT gas OCGT 0.4 True 2020 25.0 360.0 0.0 ... 0 NaN NaN 1.0 1.0 3.600000e+02 1.0 Shanxi False

55 rows × 38 columns

EXAMPLES¶